#include <iostream>
#include <vector>
#include <unordered_set>
#include <map>
#include <fstream>
#include <string>

#if defined(_WIN32)

#error "Cannot make directory for windows OS."

#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))

#include <sys/stat.h>
#include <sys/types.h>

#else
#error "Cannot make directory for an unknown OS."
#endif

namespace GraphGradient {

    double count_recall(int n_query, int topk, std::vector<std::unordered_set<int>> test_gnd_l, int *gnd,
                        std::vector<float> &recall_part_l) {

        std::vector<double> recall_l;
        double avg_recall = 0;

        for (int i = 0; i < n_query; i++) {
            int match = 0;
            for (int j = 0; j < topk; j++) {
                if (test_gnd_l[i].find(gnd[i * topk + j]) != test_gnd_l[i].end()) {
                    match++;
                }
            }
            double recall = (double) match / topk;
            recall_part_l[i] = recall;
            recall_l.push_back(recall);
            avg_recall += recall;
        }
        return avg_recall / n_query;
    }

    class SaveNode {
    public:
        const char *dataset;
        int top_k;
        const char *method_name;
        const char *graph_method;
        std::vector<std::pair<double, double>> time_recall_l;
        std::vector<std::vector<float>> recall_part_l_l;
        std::vector<int> ef_search_l;
        int n_index, total_M, ef_construction;
        std::vector<int> M_l;
        std::vector<int> indexing_time_l;
        size_t max_memory;
        std::vector<std::vector<std::pair<double, double>>> component_time_recall_l;
        std::map<std::string, std::string> other_config;

        SaveNode() {

        }

        static void save_time_recall(const std::vector<std::pair<double, double>> res, const char *path) {
            std::ofstream outfile;
            outfile.open(path);

            outfile << "time-per-query(ms), recall" << std::endl;
            int res_size = res.size();
            for (int i = 0; i < res_size; i++) {
                outfile << res[i].first << ", " << res[i].second << std::endl;
            }
            outfile << std::endl;
            outfile.close();
        }

        static void save_recall_part(const std::vector<std::vector<float>> res, const char *path) {


            FILE *fout = fopen(path, "w+");
            int l1_size = res.size();
            for (int i = 0; i < l1_size; i++) {
                int l2_size = res[i].size();
                for (int j = 0; j < l2_size; j++) {
                    std::fprintf(fout, "%4.1f ", res[i][j]);
                }
                std::fprintf(fout, "\n");
            }
            fclose(fout);
        }

        template<typename T>
        static std::string array_to_string(std::vector<T> array) {
            std::string str;
            str += "[";
            int array_size = array.size();
            for (int i = 0; i < array_size; i++) {
                str += std::to_string(array[i]);
                if (i != (int) array.size() - 1) {
                    str += ", ";
                }
            }
            str += "]";
            return str;
        }

        void save() {
            char base_path[256];
            std::sprintf(base_path, "../result/%s_%d_%s_%s_%d_%d", dataset, top_k, method_name, graph_method, total_M,
                         ef_construction);

            //mkdir
            if (mkdir(base_path, 0755) == -1) {
                printf("can not make directory, delete directory\n");
                char delete_command[256];
                std::sprintf(delete_command, "rm -rf %s", base_path);
                if (system(delete_command) == 1) {
                    printf("fail to delete the directory command");
                }
            }
            mkdir(base_path, 0755);

            // write time-recall.txt
            std::ofstream outfile;

            char time_recall_path[256];
            std::sprintf(time_recall_path, "%s/time-recall.csv", base_path);
            save_time_recall(time_recall_l, time_recall_path);

            char recall_part_path[256];
            std::sprintf(recall_part_path, "%s/part-recall.txt", base_path);
            save_recall_part(recall_part_l_l, recall_part_path);

            //write intermediate.json
            char intermediate_path[256];
            std::sprintf(intermediate_path, "%s/intermediate.json", base_path);
            outfile.open(intermediate_path);
            outfile << "{" << std::endl;
            outfile << "\"peak_memory(MB)\": " << max_memory << "," << std::endl;
            outfile << "\"indexing_build_time(s)\": ";

            std::string str = array_to_string(indexing_time_l);
            outfile << str << ", " << std::endl;

            outfile << "\"M_l\": " << array_to_string(M_l) << "," << std::endl;
            outfile << "\"n_index\": " << n_index << "," << std::endl;
            outfile << "\"ef_construction\": " << ef_construction << "," << std::endl;
            outfile << "\"ef_search_l\": ";

            str = array_to_string(ef_search_l);
            if (other_config.size() == 0) {
                outfile << str << std::endl;
            } else {
                outfile << str << ", " << std::endl;
            }

            if (component_time_recall_l.size() != 0) {
                int component_time_recall_l_size = component_time_recall_l.size();
                for (int i = 0; i < component_time_recall_l_size; i++) {
                    std::sprintf(time_recall_path, "%s/time-recall-component-%d.csv", base_path, i);
                    save_time_recall(component_time_recall_l[i], time_recall_path);
                }
            }

            int n_iter = 0;
            for (std::map<std::string, std::string>::iterator iter = other_config.begin();
                 iter != other_config.end(); iter++) {
                if (n_iter == (int) other_config.size() - 1) {
                    outfile << "\"" << iter->first << "\" : " << iter->second << std::endl;
                    break;
                }
                outfile << "\"" << iter->first << "\" : " << iter->second << ", " << std::endl;
                n_iter++;

            }

            outfile << "}" << std::endl;
        }

    };

}
